<?php

namespace App\Handlers\Commands;

use App\Entities\Asset;
use App\Entities\Vulnerability;
use App\Repositories\AssetRepository;
use App\Repositories\VulnerabilityRepository;
use App\Repositories\WorkspaceAppRepository;
use App\Services\ImageService;
use Doctrine\ORM\EntityManager;
use Illuminate\Support\Collection;

abstract class AbstractVulnerabilityHandler extends CommandHandler
{
    /** @var AssetRepository */
    protected $assetRepository;

    /** @var VulnerabilityRepository */
    protected $vulnerabilityRepository;

    /** @var WorkspaceAppRepository */
    protected $workspaceAppRepository;

    /** @var EntityManager */
    protected $em;

    /** @var ImageService */
    protected $service;

    /**
     * AbstractVulnerability constructor.
     *
     * @param AssetRepository $assetRepository
     * @param VulnerabilityRepository $vulnerabilityRepository
     * @param WorkspaceAppRepository $workspaceAppRepository
     * @param EntityManager $em
     * @param ImageService $service
     */
    public function __construct(
        AssetRepository $assetRepository, VulnerabilityRepository $vulnerabilityRepository,
        WorkspaceAppRepository $workspaceAppRepository, EntityManager $em, ImageService $service
    )
    {
        $this->assetRepository         = $assetRepository;
        $this->vulnerabilityRepository = $vulnerabilityRepository;
        $this->workspaceAppRepository  = $workspaceAppRepository;
        $this->em                      = $em;
        $this->service                 = $service;
    }

    /**
     * Get Assets that are not already related to the given Vulnerability
     *
     * @param array $assetIds
     * @param Vulnerability $vulnerability
     * @return Collection
     */
    protected function getNewlyRelatedAssets(array $assetIds, Vulnerability $vulnerability): Collection
    {
        if (empty($assetIds) || empty($vulnerability)) {
            return collect();
        }

        $existingAssetIds = collect($vulnerability->getAssets())
            ->map(function ($asset) {
                /** @var Asset $asset */
                return $asset->getId();
            });

        return collect($assetIds)->diff($existingAssetIds);
    }

    /**
     * Persist any newly related Assets
     *
     * @param array $assets
     * @param Vulnerability $vulnerability
     */
    protected function persistNewlyRelatedAssets(array $assets, Vulnerability $vulnerability)
    {
        collect($assets)->each(function ($asset) use ($vulnerability) {
            /** @var Asset $asset */
            $asset->addVulnerability($vulnerability);
            $this->em->persist($asset);
        });
    }

    /**
     * Post processing common to both creating and editing Vulnerabilities
     *
     * @param Vulnerability $vulnerability
     * @param array $assetIds
     */
    protected function doVulnerabilityPostProcessing(Vulnerability $vulnerability, array $assetIds)
    {
        // Store the thumbnails and save the stored paths in the Vulnerability
        $this->service->storeVulnerabilityThumbnails($vulnerability);

        // Persist the new Asset to the database
        $this->em->persist($vulnerability);

        // Get Asset IDs for Asset that aren't already related
        $assetIds = $this->getNewlyRelatedAssets($assetIds, $vulnerability);

        // Find new Assets to be related to the given vulnerability
        $assets = $this->assetRepository->findBy([
            Asset::ID         => $assetIds->toArray(),
            Asset::DELETED    => false,
            Asset::SUPPRESSED => false,
        ]);

        // Persist the new Asset relations
        $this->persistNewlyRelatedAssets($assets, $vulnerability);
    }
}